package org.dreamwork.websocket;

import org.apache.log4j.Logger;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;

/**
 * Created by game on 2017/2/16
 */
public abstract class AbstractWebSocketExecutor<T> extends Endpoint implements IWebSocketExecutor<T>, MessageHandler.Whole<String> {
    protected Session session;
    protected T message;
    protected ServletContext application;
    protected HttpSession httpSession;
    protected Map<String, List<String>> parameters;
    protected Map<String, List<String>> headers;
    protected String queryString;
    protected URI uri;

    private boolean locked = false;

    private final Object LOCKER = new byte [0];

    private static final Logger logger = Logger.getLogger (AbstractWebSocketExecutor.class);

    protected abstract String cast (T message);

    @Override
    @SuppressWarnings ("unchecked")
    public void onOpen (Session session, EndpointConfig config) {
        IWebSocket ws = getClass ().getAnnotation (IWebSocket.class);
        if (logger.isDebugEnabled ()) {
            logger.debug ("opening the web socket: " + ws.value () + " var " + this);
        }
        this.session = session;
        session.addMessageHandler (this);
        if (ws.cache ()) {
            if (logger.isDebugEnabled ()) {
                logger.debug ("caching myself into manager");
            }
            WebSocketManager.add (this);
        }
        Map<String, Object> props = config.getUserProperties ();
        if (ws.header ()) {
            headers = (Map<String, List<String>>) props.get (WebSocketHttpConfigurator.KEY_HTTP_HEADER);
        }
        if (ws.parameter ()) {
            parameters = (Map<String, List<String>>) props.get (WebSocketHttpConfigurator.KEY_HTTP_PARAM);
        }
        if (ws.servletContext ()) {
            application = (ServletContext) props.get (WebSocketHttpConfigurator.KEY_HTTP_CONTEXT);
        }
        if (ws.httpSession ()) {
            httpSession = (HttpSession) props.get (WebSocketHttpConfigurator.KEY_HTTP_SESSION);
        }
        queryString = (String) props.get (WebSocketHttpConfigurator.KEY_HTTP_QUERY);
        uri = (URI) props.get (WebSocketHttpConfigurator.KEY_HTTP_URI);
        onConnected ();
    }

    @Override
    public void onClose (Session session, CloseReason closeReason) {
        IWebSocket ws = getClass ().getAnnotation (IWebSocket.class);
        if (logger.isDebugEnabled ()) {
            logger.debug ("closing the web socket: " + ws.value () + "=>" + this);
        }
        if (ws.cache ()) {
            WebSocketManager.remove (this);
            if (logger.isDebugEnabled ()) {
                logger.debug ("remove myself from manager");
            }
        }

        try {
            this.session.close ();
        } catch (Exception ex) {
            logger.warn (ex.getMessage (), ex);
        }
        this.session = null;
    }

    @Override
    public void send (T message) {
        synchronized (LOCKER) {
            while (locked) {
                try {
                    LOCKER.wait ();
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
            }
            if (logger.isDebugEnabled ()) {
                logger.debug ("the lock is broken.");
            }
            locked = true;
            this.message = message;
            WebSocketManager.run (this);
//            Looper.runInOtherLoop (this);
        }
    }

    public void close () throws IOException {
        if (session != null && session.isOpen ()) {
            session.close (new CloseReason (CloseReason.CloseCodes.NORMAL_CLOSURE, null));
        }
    }

    @Override
    public void run () {
        try {
            if (session != null && session.isOpen ()) {
                String text = cast (message);
                if (logger.isDebugEnabled ()) {
                    logger.debug ("prepare to send message: " + text);
                }
                session.getBasicRemote ().sendText (text);
            }
        } catch (Exception ex) {
            logger.warn (ex.getMessage (), ex);
        } finally {
            synchronized (LOCKER) {
                locked = false;
                LOCKER.notifyAll ();
            }
        }
    }

    @Override
    public void onMessage (String message) {}

    @Override
    public boolean matches (T message) {
        return true;
    }

    @Override
    public boolean matches (String id, T message) {
        return true;
    }

    protected void onConnected () {}
}